<template>
	<div :class="treeCls" v-if="show">
		<Search v-if="filterable" v-model="searchValue" @onsearch="searchTree" block></Search>
		<ul class="h-tree-body">
			<treeItem v-for="tree of treeDatas" :data="tree" :param="param" :key="tree.key" :multiple="multiple" :status="status"
			 @trigger="trigger" :choose-mode="chooseMode" :toggleOnSelect="toggleOnSelect" :selectOnClick="selectOnClick" :level="0"></treeItem>
		</ul>
		<Loading :loading="globalloading"></Loading>
	</div>
</template>
<script>
	import utils from 'hey-utils';
	import treeItem from './treeitem';

	const prefix = 'h-tree';

	const updateChildStatus = (data, column, value) => {
		if (data.children) {
			for (let child of data.children) {
				child.status[column] = value;
				updateChildStatus(child, column, value);
			}
		}
	};

	const updateParentStatus = (objs, data, column, value) => {
		let parent = objs[data.parentKey];
		if (!utils.isNull(data.parentKey) && parent) {
			parent.status[column] = value;
			updateParentStatus(objs, parent, column, value);
		}
	};

	const updateModeAllChildChooseStatus = data => {
		if (data.children) {
			let isIndeterminateStatus = false;
			let isChoose = !!data.children.length;
			for (let child of data.children) {
				updateModeAllChildChooseStatus(child);
				if (child.status.choose || child.status.indeterminate) {
					isIndeterminateStatus = true;
				}
				if (!child.status.choose) {
					isChoose = false;
				}
			}
			if (!data.status.choose) {
				if (isChoose) {
					data.status.choose = true;
				} else if (isIndeterminateStatus) {
					data.status.indeterminate = true;
				}
			}
		}
	};

	const getChooseNode = (data, options) => {
		if (data.status.choose) {
			options.push(data.value);
		} else {
			for (let child of data.children) {
				getChooseNode(child, options);
			}
		}
		return options;
	};

	export default {
		name: 'hTree',
		props: {
			show: {
				type: Boolean,
				default: true
			},
			option: Object,
			multiple: {
				type: Boolean,
				default: false
			},
			filterable: {
				type: Boolean,
				default: false
			},
			chooseMode: {
				type: String,
				default: 'all' // independent, some, all
			},
			value: [Number, String, Array, Object],
			config: String,
			toggleOnSelect: {
				type: Boolean,
				default: true
			},
			selectOnClick: {
				type: Boolean,
				default: false
			},
			className: {
				type: String,
				default: 'h-tree-theme-item-selected'
			}
		},
		data() {
			return {
				worker: null,
				updateFromInput: false,
				globalloading: false,
				loading: true,
				status: {
					selected: null
				},
				jsonDatas: [],
				treeDatas: [],
				treeObj: {},
				searchValue: null,
				defaultConfig: {
					titleName: 'title',
					keyName: 'key',
					parentName: 'parent',
					childrenName: 'children'
				}
			};
		},
		watch: {
			value(value) {
				if (this.updateFromInput) {
					this.updateFromInput = false;
					return;
				}
				this.parse();
			},
			'option.datas': function() {
				this.initTreeDatas();
			},
			show(value){
				if(value && this.jsonDatas.length > 0){
					this.initDatas(this.jsonDatas, true)
				}
			}
		},
		mounted() {
			this.initTreeDatas();
			this.parse();
		},
		methods: {
			getJsonObj(json,id,full=false){
				let start,end,name,desc = -1
				let nameend,namedata,descdata,descend = ''
				start = json.indexOf('{"id":' + id + ',')
				end = json.indexOf('}',start)
				name = json.indexOf('"name":"', start)
				if(name > 0 && name < end){
					nameend = json.indexOf('"', name + 8)
					namedata = json.substring(name + 8, nameend)
				}
				desc = json.indexOf('"desc":"', start)
				if (desc > 0 && desc < end) {
					descend = json.indexOf('"', desc + 8)
					descdata = json.substring(desc + 8, descend)
				}
				let obj = {id:id}
				if(namedata){
					obj.name = namedata
				}
				if(descdata){
					obj.desc = descdata
				}
				if(full){
					let left = json.lastIndexOf('[', start)
					let right = json.lastIndexOf(']', start)
					let last = json.substring(json.lastIndexOf('{'))
					let len = (last.length - last.indexOf('}'))/2
					for (var i = 0; i < 10; i++) {
						if(left > right && len > 1){
							let nameleft = json.lastIndexOf('"name":"', left)
							let nameright = json.indexOf('"', nameleft+8)
							let fname = json.substring(nameleft+8, nameright)
							obj.name = fname + '/' + obj.name
							len--
						}
						start = json.lastIndexOf('{',left)
						left = json.lastIndexOf('[', start)
						right = json.lastIndexOf(']', start)
					}
				}
				if(namedata || descdata){
					return obj
				}
			},
			getDataText(){
				let arrs = []
				let json = JSON.stringify(this.jsonDatas)
				if(this.multiple){
					this.value.forEach(v => {
						let obj = this.getJsonObj(json,v)
						if(obj){
							arrs.push(obj)
						}
					})
					return arrs
				}else{
					if(this.value > 0){
						return this.getJsonObj(json,this.value,true)
					}
				}
			},
			parse() {
				if (this.multiple) {
					if (this.value instanceof Array) {
						this.updateChoose(this.value.map(Number), false);
					} else {
						this.updateChoose(this.value, false);
					}
				} else {
					this.updateSelect(this.value, false);
				}
			},
			updateTreeItem(key, value) {
				let item = this.treeObj[key];
				if (item) {
					for (let v of Object.keys(value)) {
						this.$set(item.value, v, value[v]);
						if (v == this.param.titleName) {
							item.title = value[v];
						}
					}
				}
			},
			appendTreeItem(key, value) {
				let parent = this.treeObj[key];
				let obj = this.initTreeNode(value, key);
				if (parent) {
					parent.children.push(obj);
				} else {
					this.treeDatas.push(obj);
				}
				this.treeObj[obj.key] = obj;
			},
			removeTreeItem(key) {
				let item = this.treeObj[key];
				if (item) {
					let index = this.treeDatas.indexOf(item);
					if (index > -1) {
						this.treeDatas.splice(index, 1);
					} else if (item.parentKey && this.treeObj[item.parentKey]) {
						let parent = this.treeObj[item.parentKey];
						if (parent.children.indexOf(item) > -1) {
							parent.children.splice(parent.children.indexOf(item), 1);
						}
					}
					delete this.treeObj[key];
				}
			},
			searchTree(value) {
				this.searchValue = value;
				if (!utils.isNull(this.searchValue) && this.searchValue !== '') {
					let searchValue = this.searchValue.toLowerCase();
					let fid = []
					for (let key of Object.keys(this.treeObj)) {
						let tree = this.treeObj[key];
						if ((tree.value.desc || tree.title || '').toLowerCase().indexOf(searchValue) > -1) {
							if (tree.parentKey > 0) {
								fid.push(tree.parentKey)
							}
							tree.status.hide = false;
						} else {
							tree.status.hide = true;
						}
					}
					let ffid = []
					for (let key of Object.keys(this.treeObj)) {
						let tree = this.treeObj[key];
						if (fid.indexOf(tree.key) > -1) {
							if (tree.parentKey > 0) {
								ffid.push(tree.parentKey)
							}
						}
					}
					this.expand(fid + ffid)
				} else {
					this.foldAll();
					for (let key of Object.keys(this.treeObj)) {
						let tree = this.treeObj[key];
						tree.status.hide = false;
					}
				}
			},
			trigger(params) {
				let type = params.type;
				let data = params.data;
				if (type == 'toggleTreeEvent') {
					data.status.opened = !data.status.opened;
					this.$emit('open', data.value);
				} else if (type == 'loadDataEvent') {
					if (utils.isFunction(this.param.getDatas)) {
						data.status.loading = true;
						this.param.getDatas.call(
							this.param,
							data.value,
							result => {
								data.children = this.initTreeModeData(result, true);
								data.status.isWait = false;
								data.status.loading = false;
								data.status.opened = true;
							},
							() => {
								data.status.loading = false;
							}
						);
					}
				} else if (type == 'selectEvent') {
					if (!this.multiple) {
						this.status.selected = data.key;
						this.$emit('select', data.value);
						this.setvalue();
					}
				} else if (type == 'chooseEvent') {
					let choose = data.status.choose;
					if (this.chooseMode != 'independent') {
						updateChildStatus(data, 'choose', choose);
					}
					this.$emit('choose', this.getChoose());
					if (this.multiple) this.setvalue();
				}
			},
			initTreeDatas() {
				let datas = [];
				if (utils.isArray(this.param.datas)) {
					datas = this.param.datas;
				} else if (utils.isFunction(this.param.datas)) {
					datas = this.param.datas.apply(this.param);
				}
				if (utils.isFunction(this.param.getTotalDatas) || utils.isFunction(this.param.getDatas)) {
					datas = [];
					this.globalloading = true;
					let loadData = this.param.getTotalDatas || this.param.getDatas;
					let param = [
						result => {
							this.jsonDatas = utils.copy(result)
							if(this.show){
								this.initDatas(this.jsonDatas)
							}else{
								this.$emit('loadDataSuccess')
							}
						},
						() => {
							this.globalloading = false;
						}
					];
					if (this.param.getDatas) {
						param.unshift(null);
					}
					loadData.apply(this.param, param)
				} else {
					this.initDatas(datas);
				}
			},
			generateTree(data, param) {
				if (!utils.isArray(data)) {
					console.error('[HeyUI Error] GenerateTree Error：Data must be array。');
					return null;
				}
				let result = [];
				let dataObj = utils.toObject(data, param.keyName);
				for (let d of data) {
					let parentCode = d[param.parentName];
					let hasParent = false;
					if (!utils.isNull(parentCode)) {
						let parentCodes = [parentCode];
						if (utils.isArray(parentCode)) {
							parentCodes = parentCode;
						}
						for (let code of parentCodes) {
							if (!utils.isNull(dataObj[code])) {
								hasParent = true;
								let parent = dataObj[code];
								if (!utils.isArray(parent[param.childrenName])) {
									parent[param.childrenName] = [];
								}
								parent[param.childrenName].push(d);
							}
						}
					}
					if (utils.isNull(parentCode) || !hasParent) {
						result.push(d);
					}
				}
				return result;
			},
			initDatas(datas) {
				let list = (datas = utils.copy(datas))
				if (this.param.dataMode == 'list' && datas.length > 0) {
					list = this.generateTree(datas, this.param)
				}
				let isWait = utils.isFunction(this.param.getDatas)
				this.worker = this.$worker.run((list, isWait) => {
					let objs = {}
					function initTreeModeData(list, isWait, parentKey) {
						let arrs = []
						list.forEach(v => {
							let obj = {
								key: v.id,
								title: v.name,
								value: v,
								parentKey,
								icon: v.treeIcon,
								status: {
									hide: false,
									opened: false,
									loading: false,
									checkable: v.checkable !== false,
									isWait,
									selected: false,
									indeterminate: false,
									choose: false,
									disabled: !!v.disabled
								},
								children: []
							}
							let children = v.children || []
							obj.children = initTreeModeData(children, isWait, obj.key)
							if (obj.children.length > 0) {
								obj.status.checkable = false
								obj.status.disabled = true
							}
							objs[obj.key] = obj
							arrs.push(obj)
						})
						return arrs
					}
					return {
						arrs: initTreeModeData(list, isWait),
						objs: objs
					}
				}, [list, isWait])
				this.worker.then(res => {
					this.treeDatas = res.arrs
					this.treeObj = res.objs
					this.parse()
					this.globalloading = false
					this.$emit('loadDataSuccess')
				})
				// let worker = new Worker('/worker.js')
				// worker.postMessage({list:list,isWait:isWait})
				// worker.onmessage = event => {
				// 	this.treeDatas = event.data.arrs
				// 	this.treeObj = event.data.objs
				// 	this.parse();
				// 	if(retu){
				// 		this.globalloading = false;
				// 		this.$emit('loadDataSuccess');
				// 	}
				// }
			},
			initDatasBak(datas) {
				let list = (datas = utils.copy(datas));
				if (this.param.dataMode == 'list' && datas.length > 0) {
					list = this.generateTree(datas, this.param);
				}
				let isWait = utils.isFunction(this.param.getDatas);
				let temp = this.initTreeModeData(list, isWait);
				return temp
			},
			initTreeModeData(list, isWait, parentKey) {
				let datas = [];
				for (let data of list) {
					let obj = this.initTreeNode(data, parentKey, isWait);
					let children = data[this.param.childrenName] || [];
					obj.children = this.initTreeModeData(children, isWait, obj.key);
					if (obj.children.length > 0) {
						obj.status.checkable = false
						obj.status.disabled = true
					}
					this.treeObj[obj.key] = obj;
					datas.push(obj);
				}
				return datas;
			},
			initTreeNode(data, parentKey, isWait = false) {
				return {
					key: data[this.param.keyName],
					title: data[this.param.titleName],
					value: data,
					parentKey,
					icon: data.treeIcon,
					status: {
						hide: false,
						opened: false,
						loading: false,
						checkable: data.checkable !== false,
						isWait,
						selected: false,
						indeterminate: false,
						choose: false,
						disabled: !!data.disabled
					},
					children: []
				};
			},
			refresh() {
				this.initTreeDatas();
			},
			expandAll() {
				for (let tree of Object.keys(this.treeObj)) {
					this.treeObj[tree].status.opened = true;
				}
			},
			foldAll() {
				for (let tree of Object.keys(this.treeObj)) {
					this.treeObj[tree].status.opened = false;
				}
			},
			expand(ids) {
				for (let key of Object.keys(this.treeObj)) {
					let tree = this.treeObj[key];
					tree.status.opened = ids.indexOf(tree.key) > -1;
				}
			},
			chooseAll() {
				for (let key in this.treeObj) {
					this.treeObj[key].status.choose = true;
				}
				this.setvalue();
			},
			updateSelect(key, updateValue = true) {
				if (key === null) {
					this.status.selected = null;
				} else {
					let option = this.treeObj[key];
					if (option) {
						this.status.selected = key;
						updateParentStatus(this.treeObj, option, 'opened', true);
					}
				}
				if (updateValue) {
					this.setvalue();
				}
			},
			getSelect() {
				if(!this.show && this.jsonDatas.length > 0){
					return this.getDataText()
				}else{
					if (utils.isNull(this.status.selected)) {
						return null;
					}
					let option = this.treeObj[this.status.selected];
					let obj = option.value
					for (var i = 0; i < 10; i++) {
						if(option.parentKey){
							option = this.treeObj[option.parentKey]
							obj.name = option.value.name + '/' + obj.name
						}
					}
					return obj
				}
			},
			updateChoose(choose, updateValue = true) {
				if (!this.multiple) return;
				choose = choose || [];
				for (let key of Object.keys(this.treeObj)) {
					let tree = this.treeObj[key];
					tree.status.choose = false;
					tree.status.indeterminate = false;
					tree.status.opened = false;
				}
				for (let key of choose) {
					let tree = this.treeObj[key];
					if (tree) {
						tree.status.choose = choose.indexOf(tree.key) != -1;
						if (tree.status.choose) {
							tree.status.opened = true;
							updateParentStatus(this.treeObj, tree, 'opened', true);
							if (this.chooseMode == 'all') {
								updateChildStatus(tree, 'choose', true);
							}
						}
					}
				}
				if (this.chooseMode == 'all') {
					for (let tree of this.treeDatas) {
						updateModeAllChildChooseStatus(tree);
					}
				}
				if (updateValue) {
					this.setvalue();
				}
			},
			setvalue() {
				let value = null;
				if (this.multiple) {
					let choose = this.getChoose();
					let keys = choose.map(item => item[this.param.keyName]);
					value = keys;
				} else {
					let select = this.getSelect();
					value = select ? select[this.param.keyName] : null;
				}
				this.updateFromInput = true;
				this.$emit('input', value);
				this.$emit('change', value);
				let event = document.createEvent('CustomEvent');
				event.initCustomEvent('setvalue', true, true, value);
				this.$el.dispatchEvent(event);
			},
			getFullChoose() {
				let options = [];
				for (let key of Object.keys(this.treeObj)) {
					let tree = this.treeObj[key];
					if (tree.status.choose) {
						options.push(tree.value);
					}
				}
				return options;
			},
			getChoose() {
				if(!this.show && this.jsonDatas.length > 0){
					return this.getDataText()
				}else if (this.chooseMode == 'some') {
					return this.getFullChoose();
				} else if (this.chooseMode == 'independent') {
					return this.getFullChoose();
				} else {
					let options = [];
					for (let data of this.treeDatas) {
						options = getChooseNode(data, options);
					}
					return options;
				}
			}
		},
		computed: {
			param() {
				if (this.config) {
					return utils.extend({}, this.defaultConfig, {}, this.option);
				} else {
					return utils.extend({}, this.defaultConfig, this.option);
				}
			},
			treeCls() {
				return {
					[prefix]: true,
					[`${prefix}-multiple`]: this.multiple,
					[`${prefix}-single`]: !this.multiple,
					[this.className]: !!this.className
				};
			}
		},
		components: {
			treeItem
		}
	};
</script>
